#include "stdafx.h"

#include "Resource.h"
#include "StickyTweetsAppXP.h"

#define MaxTwitterEncodedStatus 512

static StickyTweetsAppXP stickyTweetsApp;

StickyTweetsAppXP& StickyTweetsAppXP::GetInstance()
{
  return stickyTweetsApp;
}

StickyTweetsAppXP::StickyTweetsAppXP() : _settingsLoaded(FALSE)
{
  
}

StickyTweetsAppXP::~StickyTweetsAppXP()
{
}

CString StickyTweetsAppXP::GetVersion()
{
  TCHAR strVal[MAX_PATH] = { L'\0' };
  DWORD nameSize = GetModuleFileName(NULL, strVal, MAX_PATH);
  if (nameSize == 0 || nameSize == MAX_PATH)
    return L"";

  INT exeIdx = CString(strVal).ReverseFind(L'\\') + 1;

  DWORD hVersion;
  DWORD size = GetFileVersionInfoSize(&strVal[exeIdx], &hVersion);

  if (!size)
    return L"";

  BYTE* pVersionInfo = new BYTE[size];
  if (GetFileVersionInfo(&strVal[exeIdx], hVersion, size, pVersionInfo))
  {
    VS_FIXEDFILEINFO* pFileInfo(NULL);
    UINT bufLen;
    VerQueryValue(pVersionInfo, L"\\", (LPVOID*)&pFileInfo, (PUINT)&bufLen);

    WORD major = HIWORD(pFileInfo->dwFileVersionMS);
    WORD minor = LOWORD(pFileInfo->dwFileVersionMS);
    WORD build = HIWORD(pFileInfo->dwFileVersionLS);

    swprintf_s<MAX_PATH>(strVal, L"%d.%d.%d", major, minor, build);
  }
  else
  {
    strVal[0] = L'\0';
  }

  delete[] pVersionInfo;
  return strVal;
}

StickyTweetsStatus StickyTweetsAppXP::LoadSettings(bool reload)
{
  if (_settingsLoaded && !reload)
  {
    return StatusStOk;
  }
  else
  {
    StickyTweetsStatus error(StatusStOk);
    _settingsLoaded = FALSE;

    DWORD iVal(0);
    TCHAR buffer[MaxLoadString];
    DWORD bufferSize(MaxLoadString);
    
    HKEY regKey(NULL), screenNameKey(NULL);
    DWORD regCreate(0);
    _rs.LoadStringW(IDS_REG_KEY);
    if (RegCreateKeyEx(HKEY_CURRENT_USER, _rs, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &regKey, &regCreate) != ERROR_SUCCESS)
      error = ErrorRegCreate;

    if (error == StatusStOk)
    {
      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(regKey, L"LastScreenName", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS && bufferSize > 0)
      {
        if (RegOpenKey(regKey, buffer, &screenNameKey) != ERROR_SUCCESS)
          error = ErrorRegCreate;
      }
      else
      {
        _settingsLoaded = TRUE;
      }
    }

    if (error == StatusStOk)
    {
      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"ScreenName", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS)
        _settings.ScreenName = buffer;

      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"Password", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS)
        _settings.Password = buffer;

      bufferSize = sizeof(DWORD);
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"RefreshPeriod", 0, NULL, (LPBYTE)&iVal, &bufferSize) == ERROR_SUCCESS)
        _settings.RefreshPeriod = iVal;

      bufferSize = sizeof(DWORD);
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"TrimMin", 0, NULL, (LPBYTE)&iVal, &bufferSize) == ERROR_SUCCESS)
        _settings.TrimMin = iVal;

      bufferSize = sizeof(DWORD);
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"HomeMax", 0, NULL, (LPBYTE)&iVal, &bufferSize) == ERROR_SUCCESS)
        _settings.HomeMax = iVal;

      bufferSize = sizeof(DWORD);
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"NoBalloonsByDefault", 0, NULL, (LPBYTE)&iVal, &bufferSize) == ERROR_SUCCESS)
        _settings.NoBalloons = _settings.NoBalloonsByDefault = iVal > 0 ? TRUE : FALSE;
 
      bufferSize = sizeof(DWORD);
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"NoImages", 0, NULL, (LPBYTE)&iVal, &bufferSize) == ERROR_SUCCESS)
        _settings.NoImages = iVal > 0 ? TRUE : FALSE;
 
      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"TrimUser", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS)
        _settings.TrimUser = buffer;

      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"TrimPass", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS)
        _settings.TrimPass = buffer;

      bufferSize = MaxLoadString;
      SecureZeroMemory(buffer, MaxLoadString);
      if (RegQueryValueEx(screenNameKey, L"SearchFilters", 0, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS)
        _settings.SearchFilters = buffer;

      // Just being in the registry denotes that RememberMe was checked during original login
      _settings.RememberMe = TRUE;

      _settingsLoaded = TRUE;
    }

    if (screenNameKey != NULL)
      RegCloseKey(screenNameKey);

    if (regKey != NULL)
      RegCloseKey(regKey);

    return error;
  }
}

StickyTweetsStatus StickyTweetsAppXP::SaveSettings(const Settings& settings)
{
  _settings = settings;
  _settingsLoaded = TRUE;

  StickyTweetsStatus error(StatusStOk);

  if (!settings.RememberMe)
    return error;

  HKEY regKey(NULL), screenNameKey(NULL);

  _rs.LoadStringW(IDS_REG_KEY);
  if (RegCreateKeyEx(HKEY_CURRENT_USER, _rs, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &regKey, NULL) != ERROR_SUCCESS)
    error = ErrorRegCreate;

  if (error == StatusStOk)
  {
    const TCHAR* lastScreenName = settings.ScreenName.GetString();
    RegSetValueEx(regKey, L"LastScreenName", 0, REG_SZ, (const BYTE*)lastScreenName, CString::StringLength(lastScreenName) * sizeof(TCHAR));
  }

  _rs += L"\\" + settings.ScreenName;
  if (RegCreateKeyEx(HKEY_CURRENT_USER, _rs, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &screenNameKey, NULL) != ERROR_SUCCESS)
    error = ErrorRegCreate;

  if (error == StatusStOk)
  {
    const TCHAR* screenName = settings.ScreenName.GetString();
    RegSetValueEx(screenNameKey, L"ScreenName", 0, REG_SZ, (const BYTE*)screenName, CString::StringLength(screenName) * sizeof(TCHAR));

    const TCHAR* password = settings.Password.GetString();
    RegSetValueEx(screenNameKey, L"Password", 0, REG_SZ, (const BYTE*)password, CString::StringLength(password) * sizeof(TCHAR));

    RegSetValueEx(screenNameKey, L"RefreshPeriod", 0, REG_DWORD, (const BYTE*)&settings.RefreshPeriod, sizeof(DWORD));
    RegSetValueEx(screenNameKey, L"TrimMin", 0, REG_DWORD, (const BYTE*)&settings.TrimMin, sizeof(DWORD));
    RegSetValueEx(screenNameKey, L"HomeMax", 0, REG_DWORD, (const BYTE*)&settings.HomeMax, sizeof(DWORD));

    RegSetValueEx(screenNameKey, L"NoBalloonsByDefault", 0, REG_DWORD, (const BYTE*)&settings.NoBalloonsByDefault, sizeof(DWORD));
    RegSetValueEx(screenNameKey, L"NoImages", 0, REG_DWORD, (const BYTE*)&settings.NoImages, sizeof(DWORD));

    const TCHAR* trimUser = settings.TrimUser.GetString();
    RegSetValueEx(screenNameKey, L"TrimUser", 0, REG_SZ, (const BYTE*)trimUser, CString::StringLength(trimUser) * sizeof(TCHAR));

    const TCHAR* trimPass = settings.TrimPass.GetString();
    RegSetValueEx(screenNameKey, L"TrimPass", 0, REG_SZ, (const BYTE*)trimPass, CString::StringLength(trimPass) * sizeof(TCHAR));

    const TCHAR* searchFilters = settings.SearchFilters.GetString();
    RegSetValueEx(screenNameKey, L"SearchFilters", 0, REG_SZ, (const BYTE*)searchFilters, CString::StringLength(searchFilters) * sizeof(TCHAR));
  }

  if (screenNameKey != NULL)
    RegCloseKey(screenNameKey);

  if (regKey != NULL)
    RegCloseKey(regKey);

  return error;
}

Settings StickyTweetsAppXP::GetSettings()
{
  return _settings;
}

StickyTweetsStatus StickyTweetsAppXP::OpenMainContext(INTERNET_STATUS_CALLBACK callback)
{
  StickyTweetsStatus status = _mainContext.Open(_settings.ScreenName, _settings.Password, callback);
  if (status == StatusStOk)
  {
    status = _searchContext.OpenSearch(callback);
  }
  return status;
}

void StickyTweetsAppXP::GetScreenNames(CAtlList<CString>& list)
{
  StickyTweetsStatus error(StatusStOk);

  HKEY regKey(NULL);
  DWORD regCreate(0);
  _rs.LoadStringW(IDS_REG_KEY);
  if (RegCreateKeyEx(HKEY_CURRENT_USER, _rs, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &regKey, &regCreate) != ERROR_SUCCESS)
    error = ErrorRegCreate;

  if (error == StatusStOk)
  {
    TCHAR buffer[MaxLoadString];
    DWORD bufferSize(MaxLoadString);
    DWORD index(0);
    
    while (RegEnumKeyEx(regKey, index, buffer, &bufferSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) 
    {
      list.AddTail(CString(buffer));
      bufferSize = MaxLoadString;
      index++;
    }
  }

  if (regKey != NULL)
    RegCloseKey(regKey);
}

StickyTweetsStatus StickyTweetsAppXP::SetLastScreenName(const CString& screenName)
{
  StickyTweetsStatus status(StatusStOk);
  HKEY regKey(NULL);

  CString keyName;
  keyName.LoadStringW(IDS_REG_KEY);
  if (RegCreateKeyEx(HKEY_CURRENT_USER, keyName, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &regKey, NULL) != ERROR_SUCCESS)
    status = ErrorRegCreate;

  if (status == StatusStOk)
  {
    const TCHAR* lastScreenName = screenName.GetString();
    RegSetValueEx(regKey, L"LastScreenName", 0, REG_SZ, (const BYTE*)lastScreenName, CString::StringLength(lastScreenName) * sizeof(TCHAR));
  }

  return status;
}

VOID StickyTweetsAppXP::GetSearchFilterList(CAtlList<CString>& list)
{
  list.RemoveAll();

  INT pos(0);
  CString filter = _settings.SearchFilters.Tokenize(L"\n", pos);
  while (filter.GetLength() > 0)
  {
    list.AddTail(filter);
    filter = _settings.SearchFilters.Tokenize(L"\n", pos);
  }
}

StickyTweetsStatus StickyTweetsAppXP::UpdateSearchFilter(const CString& searchTerm, BOOL add)
{
  StickyTweetsStatus status(StatusStOk);
  Settings settings = GetSettings();

  if (add && !IsSearchFilter(searchTerm))
  {
    if (settings.SearchFilters.GetLength() > 0)
      settings.SearchFilters += L"\n";
    settings.SearchFilters += searchTerm;
    status = SaveSettings(settings);
  }
  else if (!add && IsSearchFilter(searchTerm))
  {
    settings.SearchFilters.Replace(searchTerm, L"");
    settings.SearchFilters.Replace(L"\n\n", L"\n");
    status = SaveSettings(settings);
  }

  if (status == StatusStOk)
    status = (add ? StatusStFiltering : StatusStUnFiltering);

  return status;
}

BOOL StickyTweetsAppXP::IsSearchFilter(const CString& searchTerm)
{
  INT pos(0);
  CString filter = _settings.SearchFilters.Tokenize(L"\n", pos);
  while (filter.GetLength() > 0)
  {
    if (filter.CompareNoCase(searchTerm) == 0)
      return TRUE;
    filter = _settings.SearchFilters.Tokenize(L"\n", pos);
  }
  return FALSE;
}

StickyTweetsStatus StickyTweetsAppXP::SetSystemError(LONG errorCode)
{
  TCHAR buffer[MaxErrorBuffer];
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, 0, buffer, MaxErrorBuffer, NULL);
  _systemError = buffer;
  return StatusStOk;
}

CString StickyTweetsAppXP::GetSystemError()
{
  return _systemError;
}

StickyTweetsStatus StickyTweetsAppXP::SetTwitterError(const CString& errorXml)
{
  INT startTagIdx(-1), endTagIdx(-1);
  if ((startTagIdx = errorXml.Find(L"<error>")) >= 0 && (endTagIdx = errorXml.Find(L"</error>")) >= 0)
  {
    _twitterError = errorXml.Mid(startTagIdx + 7, endTagIdx - (startTagIdx + 7));
    ATLTRACE2(L"Twitter error: %s\n", _twitterError.GetString());
    return StatusStOk;
  }
  else
  {
    _twitterError = L"";
    return ErrorStExtractTwitterError;
  }
}

CString StickyTweetsAppXP::GetTwitterError(VOID)
{
  return _twitterError;
}

StickyTweetsStatus StickyTweetsAppXP::VerifyCredentials(const TCHAR* screenName, const TCHAR* password)
{
  StickyTweetsStatus error(StatusStOk);

  CString urlTwitter;
  urlTwitter.LoadStringW(IDS_URL_TWITTER);
  if (!InternetCheckConnection(urlTwitter, FLAG_ICC_FORCE_CONNECTION, 0))
    error = ErrorInternetConnection;

  HINTERNET hInternet(NULL);

  _rs.LoadStringW(IDS_APP_TITLE);
  if (error == StatusStOk && (hInternet = InternetOpen(_rs, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) == NULL)
    error = ErrorInternetOpen;

  HINTERNET hTwitter(NULL);
  
  if (error == StatusStOk && (hTwitter = InternetConnect(hInternet, urlTwitter.Mid(7), INTERNET_DEFAULT_HTTP_PORT, screenName, password, INTERNET_SERVICE_HTTP, 0, NULL)) == NULL)
    error = ErrorInternetConnect;

  HINTERNET hRequest(NULL);
  
  _rs.LoadStringW(IDS_SO_TWITTER_VERIFY);
  if (error == StatusStOk && (hRequest = HttpOpenRequest(hTwitter, L"GET", _rs, NULL, NULL, NULL, INTERNET_FLAG_RELOAD, NULL)) == NULL)
    error = ErrorInternetOpenRequest;

  if (error == StatusStOk && !HttpSendRequest(hRequest, NULL, 0L, NULL, 0))
    error = ErrorInternetSendRequest;

  if (error == StatusStOk)
  {
    DWORD statusCode;
    error = GetStatusCode(hRequest, &statusCode);
    
    if (error == StatusStOk && statusCode != HTTP_STATUS_OK)
    {
      CString twitterError(L"");
      if (ReadResponse(hRequest, twitterError, TRUE) == StatusStOk && SetTwitterError(twitterError) == StatusStOk)
        error = ErrorTwitterVerify;
      else
        error = ErrorStExtractTwitterError;
    }
  }

  if (hRequest != NULL)
    InternetCloseHandle(hRequest);
  if (hTwitter != NULL)
    InternetCloseHandle(hTwitter);
  if (hInternet != NULL)
    InternetCloseHandle(hInternet);

  return error;
}

StickyTweetsStatus StickyTweetsAppXP::GetCurrentTimelineXml(CString& xml, CString sinceId)
{
  if (IsRequested(RtCurrentTimeline))
  {
    return StatusStOk;
  }

  StickyTweetsStatus status = _mainContext.Verify(_settings.ScreenName, _settings.Password);

  RequestContext* pRequestContext(NULL);

  if (status == StatusStOk)
  {
    pRequestContext = new RequestContext(&_mainContext);
    _requestContexts.AddTail(pRequestContext);

    if (sinceId.GetLength() > 0)
      _rs.Format(IDSF_TWITTER_CURTIMELINESINCE, sinceId);
    else
      _rs.LoadStringW(IDS_SO_TWITTER_CURTIMELINE);

    status = pRequestContext->SendRequest(RtCurrentTimeline, _rs, sinceId);
  }

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

StickyTweetsStatus StickyTweetsAppXP::GetMentions(CString& xml, CString sinceId)
{
  if (IsRequested(RtMentions))
  {
    return StatusStOk;
  }

  StickyTweetsStatus status = _mainContext.Verify(_settings.ScreenName, _settings.Password);

  RequestContext* pRequestContext(NULL);

  if (status == StatusStOk)
  {
    pRequestContext = new RequestContext(&_mainContext);
    _requestContexts.AddTail(pRequestContext);

    if (sinceId.GetLength() > 0)
      _rs.Format(IDSF_TWITTER_MENTIONSSINCE, sinceId);
    else
      _rs.LoadStringW(IDS_TWITTER_MENTIONS);

    status = pRequestContext->SendRequest(RtMentions, _rs, sinceId);
  }

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

StickyTweetsStatus StickyTweetsAppXP::GetDirectXml(CString& xml, CString sinceId)
{
  if (IsRequested(RtDirect))
  {
    return StatusStOk;
  }

  StickyTweetsStatus status = _mainContext.Verify(_settings.ScreenName, _settings.Password);

  RequestContext* pRequestContext(NULL);

  if (status == StatusStOk)
  {
    pRequestContext = new RequestContext(&_mainContext);
    _requestContexts.AddTail(pRequestContext);

    if (sinceId.GetLength() > 0)
      _rs.Format(IDSF_TWITTER_DIRECTSINCE, sinceId);
    else
      _rs.LoadStringW(IDS_TWITTER_DIRECT);

    status = pRequestContext->SendRequest(RtDirect, _rs, sinceId);
  }

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

StickyTweetsStatus StickyTweetsAppXP::GetSentDirectXml(CString& xml, CString sinceId)
{
  if (IsRequested(RtSentDirect))
  {
    return StatusStOk;
  }

  StickyTweetsStatus status = _mainContext.Verify(_settings.ScreenName, _settings.Password);

  RequestContext* pRequestContext(NULL);

  if (status == StatusStOk)
  {
    pRequestContext = new RequestContext(&_mainContext);
    _requestContexts.AddTail(pRequestContext);

    if (sinceId.GetLength() > 0)
      _rs.Format(IDSF_TWITTER_SENTDIRECTSINCE, sinceId);
    else
      _rs.LoadStringW(IDS_TWITTER_SENTDIRECT);

    status = pRequestContext->SendRequest(RtSentDirect, _rs, sinceId);
  }

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

StickyTweetsStatus StickyTweetsAppXP::GetUserTimelineXml(const CString& screenName, CString& xml)
{
  if (IsRequested(RtUserTimeline))
  {
    return StatusStOk;
  }

  StickyTweetsStatus status = _mainContext.Verify(_settings.ScreenName, _settings.Password);

  RequestContext* pRequestContext(NULL);

  if (status == StatusStOk)
  {
    pRequestContext = new RequestContext(&_mainContext);
    _requestContexts.AddTail(pRequestContext);

    _rs.Format(IDSF_SO_TWITTER_USERTIMELINE, screenName);

    status = pRequestContext->SendRequest(RtUserTimeline, _rs, screenName);
  }

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

StickyTweetsStatus StickyTweetsAppXP::GetSearchAtom(const CString& query, CString& atomXml)
{
  if (IsRequested(RtSearch))
  {
    return StatusStOk;
  }

  RequestContext* pRequestContext = new RequestContext(&_searchContext);
  _requestContexts.AddTail(pRequestContext);

  CString encodedQuery = EncodeUri(query);
  CString resource = CString(L"/search.atom?q=") + encodedQuery;
  StickyTweetsStatus status = pRequestContext->SendRequest(RtSearch, resource, query);

  if (status == StatusStOk)
  {
    return StatusStAsync;
  }
  else
  {
    return status;
  }
}

RequestContext* StickyTweetsAppXP::GetRequestContext(HINTERNET hInternet)
{
  POSITION pos = _requestContexts.GetHeadPosition();
  while (pos)
  {
    RequestContext* pRequest = _requestContexts.GetNext(pos);
    if (pRequest->Request == hInternet)
    {
      return pRequest;
    }
  }
  return NULL;
}

StickyTweetsStatus StickyTweetsAppXP::DeleteRequestContext(HINTERNET hInternet)
{
  ATLTRACE2(L"  DeleteRequestContext for HINTERNET %x\n", hInternet);

  POSITION pos = _requestContexts.GetHeadPosition();
  while (pos)
  {
    POSITION current = pos;
    RequestContext* pRequest = _requestContexts.GetNext(pos);
    if (pRequest->Request == hInternet)
    {
      _requestContexts.RemoveAt(current);
      break;
    }
  }

#ifdef _DEBUG
  ATLTRACE2(L"  These HINTERNETs still exist: ");
  pos = _requestContexts.GetHeadPosition();
  while (pos)
  {
    RequestContext* pRequest = _requestContexts.GetNext(pos);
    ATLTRACE2(L"%x, ", pRequest->Request);
  }
  ATLTRACE2(L"\n");
#endif

  return StatusStOk;
}

BOOL StickyTweetsAppXP::IsRequested(RequestType type)
{
  POSITION pos = _requestContexts.GetHeadPosition();
  while (pos)
  {
    if (_requestContexts.GetNext(pos)->Type == type)
    {
      return TRUE;
    }
  }
  return FALSE;
}

StickyTweetsStatus StickyTweetsAppXP::ResetMainContext()
{
  while (_requestContexts.GetCount() > 0)
  {
    RequestContext* pRequest = _requestContexts.GetHead();
    InternetCloseHandle(pRequest->Request);
    delete pRequest;
    _requestContexts.RemoveHead();
  }
  return _mainContext.Close();
}

BOOL StickyTweetsAppXP::IsFollowing(const CString& screenName)
{
  CString response, objectName;
  objectName.Format(IDSF_TWITTER_ISFOLLOWING, _settings.ScreenName, screenName);
  StickyTweetsStatus error = GetWebResponse(L"twitter.com", objectName, _settings.ScreenName, _settings.Password, response, FALSE);

  if (error == StatusStOk)
    return (response.Find(L"true") > 0 ? TRUE : FALSE);
  else
    return FALSE;
}


StickyTweetsStatus StickyTweetsAppXP::DownloadImage(const CString& imageUrl, Image** image)
{
  CString localFile;
  StickyTweetsStatus error = DownloadImageToCache(imageUrl, localFile);

  if (error == StatusStOk)
    *image = new Image(localFile);

  return error;  
}

StickyTweetsStatus StickyTweetsAppXP::UpdateStatus(const CString& status, const CString& inReplyToId)
{
  StickyTweetsStatus error(StatusStOk);

  CString encodedStatus = EncodeUri(status);

  if (!InternetCheckConnection(L"http://twitter.com", FLAG_ICC_FORCE_CONNECTION, 0))
    error = ErrorInternetConnection;

  HINTERNET hInternet(NULL);
  
  if (error == StatusStOk && (hInternet = InternetOpen(L"StickyTweets", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) == NULL)
    error = ErrorInternetOpen;

  HINTERNET hTwitter(NULL);
  
  if (error == StatusStOk && (hTwitter = InternetConnect(hInternet, L"twitter.com", INTERNET_DEFAULT_HTTP_PORT, _settings.ScreenName, _settings.Password, INTERNET_SERVICE_HTTP, 0, NULL)) == NULL)
    error = ErrorInternetConnect;

  CString url;
  url.Format(IDSF_TWITTER_UPDATE, inReplyToId, encodedStatus);
  HINTERNET hRequest(NULL);

  if (error == StatusStOk && (hRequest = HttpOpenRequest(hTwitter, L"POST", url, NULL, NULL, NULL, INTERNET_FLAG_RELOAD, NULL)) == NULL)
    error = ErrorInternetOpenRequest;

  if (error == StatusStOk && !HttpSendRequest(hRequest, NULL, 0L, NULL, 0))
    error = ErrorInternetSendRequest;

  if (error == StatusStOk)
  {
    DWORD statusCode;
    error = GetStatusCode(hRequest, &statusCode);
    
    if (error == StatusStOk && statusCode != HTTP_STATUS_OK)
    {
      CString twitterError(L"");
      if (ReadResponse(hRequest, twitterError, TRUE) == StatusStOk && SetTwitterError(twitterError) == StatusStOk)
        error = ErrorTwitterError;
      else
        error = ErrorStExtractTwitterError;
    }
  }

  CString xmlResponse(L"");

  if (error == StatusStOk)
    error = ReadResponse(hRequest, xmlResponse, TRUE);

  if (hRequest != NULL)
    InternetCloseHandle(hRequest);
  if (hTwitter != NULL)
    InternetCloseHandle(hTwitter);
  if (hInternet != NULL)
    InternetCloseHandle(hInternet);

  return error;
}

StickyTweetsStatus StickyTweetsAppXP::SendDm(const CString& user, const CString& text)
{
  StickyTweetsStatus error(StatusStOk);

  CString encodedText = EncodeUri(text);

  if (!InternetCheckConnection(L"http://twitter.com", FLAG_ICC_FORCE_CONNECTION, 0))
    error = ErrorInternetConnection;

  HINTERNET hInternet(NULL);
  
  if (error == StatusStOk && (hInternet = InternetOpen(L"StickyTweets", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) == NULL)
    error = ErrorInternetOpen;

  HINTERNET hTwitter(NULL);
  
  if (error == StatusStOk && (hTwitter = InternetConnect(hInternet, L"twitter.com", INTERNET_DEFAULT_HTTP_PORT, _settings.ScreenName, _settings.Password, INTERNET_SERVICE_HTTP, 0, NULL)) == NULL)
    error = ErrorInternetConnect;

  CString url;
  url.Format(IDSF_TWITTER_SENDDM, user, encodedText);
  HINTERNET hRequest(NULL);

  if (error == StatusStOk && (hRequest = HttpOpenRequest(hTwitter, L"POST", url, NULL, NULL, NULL, INTERNET_FLAG_RELOAD, NULL)) == NULL)
    error = ErrorInternetOpenRequest;

  if (error == StatusStOk && !HttpSendRequest(hRequest, NULL, 0L, NULL, 0))
    error = ErrorInternetSendRequest;

  if (error == StatusStOk)
  {
    DWORD statusCode;
    error = GetStatusCode(hRequest, &statusCode);
    
    if (error == StatusStOk && statusCode != HTTP_STATUS_OK)
    {
      CString twitterError(L"");
      if (ReadResponse(hRequest, twitterError, TRUE) == StatusStOk && SetTwitterError(twitterError) == StatusStOk)
        error = ErrorTwitterError;
      else
        error = ErrorStExtractTwitterError;
    }
  }

  CString xmlResponse(L"");

  if (error == StatusStOk)
    error = ReadResponse(hRequest, xmlResponse, TRUE);

  if (hRequest != NULL)
    InternetCloseHandle(hRequest);
  if (hTwitter != NULL)
    InternetCloseHandle(hTwitter);
  if (hInternet != NULL)
    InternetCloseHandle(hInternet);

  if (error == StatusStOk)
    error = StatusStDmOk;

  return error;
}

StickyTweetsStatus StickyTweetsAppXP::Follow(const CString& screenName, BOOL follow)
{
  CString xmlResponse;
  if (follow)
    _rs.Format(IDSF_TWITTER_FOLLOW, screenName);
  else
    _rs.Format(IDSF_TWITTER_UNFOLLOW, screenName);

  StickyTweetsStatus error = GetWebResponse(L"twitter.com", _rs, _settings.ScreenName, _settings.Password, xmlResponse, TRUE, L"POST");

  if (error == StatusStOk)
    error = (follow ? StatusStFollowing : StatusStUnFollowing);

  return error;
}


StickyTweetsStatus StickyTweetsAppXP::TrimUrl(const CString& original, CString& trimmed)
{
  if (IsTrimmed(original))
  {
    trimmed = original;
    return StatusStOk;
  }

  StickyTweetsAppXP& app = StickyTweetsAppXP::GetInstance();
  Settings settings = app.GetSettings();

  CString encodedOriginal = EncodeUri(original);

  CString xmlResponse(L"");
  CString objectName;
  objectName.Format(L"/api/trim_url.xml?username=%s&password=%s&url=%s", settings.TrimUser, settings.TrimPass, encodedOriginal);
  StickyTweetsStatus error = GetWebServiceXml(L"api.tr.im", objectName, settings.TrimUser, settings.TrimPass, xmlResponse);

  if (error == StatusStOk)
  {
    INT startTagIdx(-1), endTagIdx(-1);
    if ((startTagIdx = xmlResponse.Find(L"<url>")) >= 0 && (endTagIdx = xmlResponse.Find(L"</url>")) >= 0)
    {
      trimmed = xmlResponse.Mid(startTagIdx + 5, endTagIdx - (startTagIdx + 5));
    }
    else
    {
      trimmed = L"";
      error = ErrorTrimError;
    }
  }

  return error;
}

BOOL StickyTweetsAppXP::IsTrimmed(const CString& url)
{
  if (url.GetLength() <= (INT)_settings.TrimMin)
    return TRUE;

  if (url.Find(L"http://tr.im") >= 0 ||
    url.Find(L"http://tinyurl.com") >= 0 ||
    url.Find(L"http://twurl.nl") >= 0 ||
    url.Find(L"http://ff.im") >= 0 ||
    url.Find(L"http://bit.ly") >= 0 ||
    url.Find(L"http://is.gd") >= 0)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

CString StickyTweetsAppXP::EncodeUri(const CString& uri)
{
  static PTCHAR reservedChars[22] = { L"%",   L"!",   L"*",   L"'",   L"(",   L")",   L";",   L":",   L"@",   L"&",   L"=",   L"+",   L"$",   L",",   L"/",   L"?",   L"#",   L"[",   L"]",   L"\"",  L"<",        L">" };
  static PTCHAR encodedChars[22] =  { L"%25", L"%21", L"%2A", L"%27", L"%28", L"%29", L"%3B", L"%3A", L"%40", L"%26", L"%3D", L"%2B", L"%24", L"%2C", L"%2F", L"%3F", L"%23", L"%5B", L"%5D", L"%22", L"%26lt%3B", L"%26gt%3B" };

  CString encodedUri(uri);
  for (INT i = 0; i < 22; i++)
    encodedUri.Replace(reservedChars[i], encodedChars[i]);

  CString encodedUnicodeUri;
  LPTSTR buffer = encodedUri.GetBuffer();
  for (INT i = 0; buffer[i] != NULL; i++)
  {
    CString curChar(buffer[i]);
    INT curVal = static_cast<INT>(buffer[i]);
    if (curVal >= 128 && curVal < 256)
    {
      curChar.Format(L"%%26%%23x%02x%%3B", static_cast<BYTE>(buffer[i]));
    }
    else if (curVal >= 256)
    {
      curChar.Format(L"%%26%%23x%02x%02x%%3B", static_cast<BYTE>(buffer[i] >> 8), static_cast<BYTE>(buffer[i]));
    }

    encodedUnicodeUri.Append(curChar);
  }

  return encodedUnicodeUri;
}

CTime StickyTweetsAppXP::ParseDate(const CString& date)
{
  INT year, month, day, hour, min, sec;
  CTime ret;

  if (date.Find(L"GMT") >= 0) // RFC 1123
  {
    TCHAR* toks = L" :";
    INT iVal;
    INT pos(0);

    CString part = date.Tokenize(toks, pos);
    if (part.GetLength() == 0) // Skip day of week
      return ret;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    day = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;

    CString months[] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
    INT i = 0;
    for (; i < 12; i++)
    {
      if (months[i].Compare(part) == 0)
      {
        month = i + 1;
        break;
      }
    }
    if (i == 12)
      return ret;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    year = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    hour = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    min = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    sec = iVal;
  }
  else if (date.Find(L" ") >= 0) // UTC
  {
    TCHAR* toks = L" :";
    INT iVal;
    INT pos(0);

    CString part = date.Tokenize(toks, pos);
    if (part.GetLength() == 0) // Skip day of week
      return ret;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;

    CString months[] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" };
    INT i = 0;
    for (; i < 12; i++)
    {
      if (months[i].Compare(part) == 0)
      {
        month = i + 1;
        break;
      }
    }
    if (i == 12)
      return ret;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    day = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    hour = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    min = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    sec = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0) // Skip offset for now
      return ret;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    year = iVal;
  }
  else // ISO 8601
  {
    TCHAR* toks = L"-T:";
    INT iVal;
    INT pos(0);

    CString part = date.Tokenize(toks, pos);
    if (part.GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    year = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    month = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    day = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    hour = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    min = iVal;

    if ((part = date.Tokenize(toks, pos)).GetLength() == 0)
      return ret;
    if (swscanf_s(part, L"%d", &iVal) != 1)
      return ret;
    sec = iVal;
  }

  ret = CTime(year, month, day, hour, min, sec);

  TIME_ZONE_INFORMATION tzInfo;
  DWORD inDaylight = GetTimeZoneInformation(&tzInfo);
  ret -= CTimeSpan(0, 0, tzInfo.Bias, 0);
  ret -= CTimeSpan(0, 0, inDaylight == TIME_ZONE_ID_DAYLIGHT ? tzInfo.DaylightBias : tzInfo.StandardBias, 0);
  return ret;
}

CString StickyTweetsAppXP::FormatDate(const CString& date)
{
  CTime dateTime = ParseDate(date);
  return dateTime.Format(L"%a %B %d, %Y %I:%M") + dateTime.Format(L"%p").MakeLower();
}

StickyTweetsStatus StickyTweetsAppXP::DownloadImageToCache(const CString& imageUrl, CString& localFilename)
{
  StickyTweetsStatus error(StatusStOk);

  BYTE* cacheEntryBytes(NULL);
  LPINTERNET_CACHE_ENTRY_INFO cacheEntry(NULL);
  DWORD cacheEntrySize(0);

  // This will fail and get us either the full size of the cache entry or tell us to create new cache entry
  GetUrlCacheEntryInfo(imageUrl, NULL, &cacheEntrySize);

  TCHAR newCacheFilename[MAX_PATH];
  CAtlFile cacheFile;

  switch (GetLastError())
  {
  case ERROR_FILE_NOT_FOUND:
    if (!CreateUrlCacheEntry(imageUrl, 0, imageUrl.Right(3), newCacheFilename, 0))
      error = ErrorInternetCreateCache;

    if (error == StatusStOk && FAILED(cacheFile.Create(newCacheFilename, FILE_GENERIC_WRITE, 0, CREATE_ALWAYS)))
      error = ErrorInternetCreateCache;

    if (error == StatusStOk)
    {
      if (!InternetCheckConnection(imageUrl, FLAG_ICC_FORCE_CONNECTION, 0))
        error = ErrorInternetConnection;

      HINTERNET hInternet(NULL);
      
      if (error == StatusStOk && (hInternet = InternetOpen(L"StickyTweets", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) == NULL)
        error = ErrorInternetOpen;

      INT hostStartIdx = imageUrl.Find(L"//");
      INT hostEndIdx = imageUrl.Find(L"/", hostStartIdx + 2);
      CString hostName(L"");

      if (hostStartIdx <= 0 || hostEndIdx <= 0)
        error = ErrorInternetCrackUrl;

      if (error == StatusStOk)
        hostName = imageUrl.Mid(hostStartIdx + 2, hostEndIdx - hostStartIdx - 2);

      HINTERNET hImageServer(NULL);

      if (error == StatusStOk && (hImageServer = InternetConnect(hInternet, hostName.GetString(), INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL)) == NULL)
        error = ErrorInternetConnect;

      HINTERNET hRequest(NULL);
      
      LPCWSTR acceptTypes[4] = {L"image/gif", L"image/jpeg", L"image/png", NULL};
      if (error == StatusStOk && (hRequest = HttpOpenRequest(hImageServer, L"GET", imageUrl.Mid(hostEndIdx), NULL, NULL, acceptTypes, INTERNET_FLAG_NEED_FILE, NULL)) == NULL)
        error = ErrorInternetOpenRequest;

      if (error == StatusStOk && !HttpSendRequest(hRequest, NULL, 0L, NULL, 0))
        error = ErrorInternetSendRequest;

      if (error == StatusStOk)
      {
        DWORD statusCode;
        error = GetStatusCode(hRequest, &statusCode);
        
        if (error == StatusStOk && statusCode != HTTP_STATUS_OK)
          error = ErrorInternetDownloadResponse;
      }

      BYTE buffer[MaxResponseBuffer];
      BOOL doneReading(FALSE);

      while (!doneReading)
      {
        DWORD bytesRead;
        BOOL readOk = InternetReadFile(hRequest, buffer, MaxResponseBuffer, &bytesRead);

        if (!readOk)
        {
          error = ErrorInternetRead;
          break;
        }

        if (bytesRead == 0)
        {
          doneReading = TRUE;
        }
        else
        {
          cacheFile.Write(buffer, bytesRead);
        }
      }

      cacheFile.Close();

      FILETIME zeroTime = {0, 0};
      if (error == StatusStOk && !CommitUrlCacheEntry(imageUrl, newCacheFilename, zeroTime, zeroTime, NORMAL_CACHE_ENTRY, NULL, 0, NULL, NULL))
        error = ErrorInternetCreateCache;

      if (hRequest != NULL)
        InternetCloseHandle(hRequest);
      if (hImageServer != NULL)
        InternetCloseHandle(hImageServer);
      if (hInternet != NULL)
        InternetCloseHandle(hInternet);

      if (error == StatusStOk)
        localFilename = newCacheFilename;
    }
    break;

  case ERROR_INSUFFICIENT_BUFFER:
    cacheEntryBytes = new BYTE[cacheEntrySize];
    cacheEntry = (LPINTERNET_CACHE_ENTRY_INFO)cacheEntryBytes;
    if (GetUrlCacheEntryInfo(imageUrl, cacheEntry, &cacheEntrySize))
      localFilename = cacheEntry->lpszLocalFileName;
    else
      error = ErrorInternetDownloadFile;
    delete[] cacheEntryBytes;
    break;

  default:
    return ErrorInternetDownloadFile;
  }

  return error;
}

StickyTweetsStatus StickyTweetsAppXP::GetStatusCode(HINTERNET hRequest, LPDWORD statusCode)
{
  DWORD statusCodeSize = sizeof(DWORD);
  DWORD headerIndex(0L);

  if (!HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, (LPVOID)statusCode, &statusCodeSize, &headerIndex))
    return ErrorInternetQueryStatus;
  else
    return StatusStOk;
}

StickyTweetsStatus StickyTweetsAppXP::GetTwitterXml(const TCHAR* objectName, CString& xmlResponse)
{
  return GetTwitterXml(L"twitter.com", objectName, xmlResponse);
}


StickyTweetsStatus StickyTweetsAppXP::GetTwitterXml(const TCHAR* server, const TCHAR* objectName, CString& xmlResponse)
{
  return GetWebServiceXml(server, objectName, _settings.ScreenName, _settings.Password, xmlResponse);
}

StickyTweetsStatus StickyTweetsAppXP::GetWebServiceXml(const TCHAR* server, const TCHAR* objectName, const TCHAR* userName, const TCHAR* password, CString& xmlResponse)
{
  return GetWebResponse(server, objectName, userName, password, xmlResponse, TRUE);
}

StickyTweetsStatus StickyTweetsAppXP::GetWebResponse(const TCHAR* server, const TCHAR* objectName, const TCHAR* userName, const TCHAR* password, CString& response, BOOL requireXml, const TCHAR* verb)
{
  StickyTweetsStatus error(StatusStOk);

  if (!InternetCheckConnection(CString(L"http://") + server, FLAG_ICC_FORCE_CONNECTION, 0))
    error = ErrorInternetConnection;

  HINTERNET hInternet(NULL);
  
  if (error == StatusStOk && (hInternet = InternetOpen(L"StickyTweets", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) == NULL)
    error = ErrorInternetOpen;

  HINTERNET hTwitter(NULL);
  
  if (error == StatusStOk && (hTwitter = InternetConnect(hInternet, server, INTERNET_DEFAULT_HTTP_PORT, userName, password, INTERNET_SERVICE_HTTP, 0, NULL)) == NULL)
    error = ErrorInternetConnect;

  HINTERNET hRequest(NULL);
  
  if (error == StatusStOk && (hRequest = HttpOpenRequest(hTwitter, verb, objectName, NULL, NULL, NULL, INTERNET_FLAG_RELOAD, NULL)) == NULL)
    error = ErrorInternetOpenRequest;

  if (error == StatusStOk && !HttpSendRequest(hRequest, NULL, 0L, NULL, 0))
    error = ErrorInternetSendRequest;

  if (error == StatusStOk)
  {
    DWORD statusCode;
    error = GetStatusCode(hRequest, &statusCode);
    
    if (error == StatusStOk && statusCode >= 300)
    {
      CString twitterError(L"");
      if (ReadResponse(hRequest, twitterError, requireXml) == StatusStOk && SetTwitterError(twitterError) == StatusStOk)
      {
        error = ErrorTwitterError;
      }
      else
      {
        error = ErrorStExtractTwitterError;
      }
    }
  }

  response.Empty();

  if (error == StatusStOk)
    error = ReadResponse(hRequest, response, requireXml);

  if (hRequest != NULL)
    InternetCloseHandle(hRequest);
  if (hTwitter != NULL)
    InternetCloseHandle(hTwitter);
  if (hInternet != NULL)
    InternetCloseHandle(hInternet);

  return error;
}

StickyTweetsStatus StickyTweetsAppXP::ReadResponse(HINTERNET hRequest, CString& response, BOOL requireXml)
{
  StickyTweetsStatus error(StatusStOk);

  BYTE buffer[MaxResponseBuffer];
  BOOL isXml(FALSE);
  BOOL doneReading(FALSE);

  while (!doneReading)
  {
    DWORD bytesRead;
    BOOL readOk = InternetReadFile(hRequest, buffer, MaxResponseBuffer - 1, &bytesRead);

    if (!readOk)
    {
      error = ErrorInternetRead;
      break;
    }

    if (bytesRead == 0)
    {
      doneReading = TRUE;
    }
    else
    {
      buffer[MaxResponseBuffer - 1] = '\0';
      CString textBuffer(buffer);
      isXml = (isXml || textBuffer.Find(L"<?xml ") >= 0) ? TRUE : FALSE;
      if (isXml || !requireXml)
      {
        response.Append(textBuffer);
      }
      else
      {
        error = ErrorStTwitterXml;
        break;
      }
    }
  }

  return error;
}
